/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.user.myapplication;

import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.Formatter;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

/**
 * Service for managing connection and data communication with a GATT server hosted on a
 * given Bluetooth LE device.
 * 用于管理连接和数据服务托管在GATT服务器通信给定蓝牙设备。
 */
public class BluetoothLeService extends Service {
    	private final static String TAG = BluetoothLeService.class.getSimpleName();

    	private BluetoothManager mBluetoothManager;
    	private BluetoothAdapter mBluetoothAdapter;
    	private String mBluetoothDeviceAddress;
			//作为中央来使用和处理数据。。。。。关键类
    	private BluetoothGatt mBluetoothGatt;
    	private int mConnectionState = STATE_DISCONNECTED;
			//作为周边来提供数据
			//每一个周边BluetoothGattServer，包含多个服务Service，每一个Service包含多个特征Characteristic。
			//BluetoothGattServerCallback返回周边的状态。BluetoothGattCallback返回中央的状态和周边提供的数据。
		private BluetoothGattService mOadService;
		private BluetoothGattService mConnControlService;
		private List<BluetoothGattCharacteristic> mCharListOad;
		private List<BluetoothGattCharacteristic> mCharListCc;
		private DeviceControlActivity mDeviceActivity = null;

		private BluetoothGattCharacteristic mCharBlock = null;
		public BluetoothGattCharacteristic mCharIdentify = null;
		private BluetoothGattCharacteristic mCharConnReq = null;
		private BluetoothGattCharacteristic characteristic_sendpic;
    	private int mSendCount = 0;
    	private int mFileLen = 0;
		private byte mchecksum = 0;
		private byte mretrytran = 0;
		private byte[] mdataByte = new byte[20];
		private boolean mImageVer = false;
		
		private boolean mServiceOk = false;
		private volatile boolean mBusy = false; // Write/read pending response
		private File mfile;
    	public static final byte TRANSMIT_LEN = 16;
		private int mtotal;
			//文件输入流
		public FileInputStream  streampic;

		private boolean mUpdateFirmwareOn = false;

		// Programming parameters      编程参数
		private static final short OAD_CONN_MIN_INTERVAL = 16; // 6; // 16; // 20mec 10; // 12.5 msec
		private static final short OAD_CONN_MAX_INTERVAL = 32; // 12; // 32; // 20mec 10; // 12.5 msec
		private static final short OAD_SUPERVISION_TIMEOUT = 100; // 1 second
		private static final int PKT_INTERVAL = 40; // Milliseconds
		private static final int GATT_WRITE_TIMEOUT = 100; // Milliseconds
		private static final int FILE_BUFFER_SIZE = 0x40000;
		private static final int OAD_BLOCK_SIZE = 16;
		private static final int HAL_FLASH_WORD_SIZE = 4;
		private static final int OAD_BUFFER_SIZE = 2 + OAD_BLOCK_SIZE;
		private static final int OAD_IMG_HDR_SIZE = 8;
		// Programming
		private final byte[] mFileBuffer = new byte[FILE_BUFFER_SIZE];
		private final byte[] mOadBuffer = new byte[OAD_BUFFER_SIZE];
		public ImgHdr mFileImgHdr = new ImgHdr();
		public ImgHdr mTargImgHdr = new ImgHdr();
		private Timer mTimer = null;
		private ProgInfo mProgInfo = new ProgInfo();
		private TimerTask mTimerTask = null;
		// Housekeeping
		private boolean mProgramming = false;
		private int mEstDuration = 0;
    	private static final int STATE_DISCONNECTED = 0;
    	private static final int STATE_CONNECTING = 1;
    	private static final int STATE_CONNECTED = 2;

    	public final static String ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
    	public final static String ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
    	public final static String ACTION_GATT_SERVICES_DISCOVERED =
            "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
    	public final static String ACTION_DATA_AVAILABLE =
            "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
    	public final static String ACTION_WARNNING =
            "com.example.bluetooth.le.ACTION_WARNNING";
    	public final static String EXTRA_DATA =
            "com.example.bluetooth.le.EXTRA_DATA";
		public final static String EXTRA_UUID = 
						"com.example.bluetooth.le.EXTRA_UUID";

    	public final static UUID UUID_HEALTH_SMOKE_INFO =
            UUID.fromString(SampleGattAttributes.HEALTH_SMOKE_INFO);
    
    	public final static UUID UUID_HEALTH_SMOKE_CMD =
            UUID.fromString(SampleGattAttributes.HEALTH_SMOKE_CMD);
    
    	public final static UUID UUID_HEALTH_SMOKE_PARA_TO_APP =
            UUID.fromString(SampleGattAttributes.HEALTH_SMOKE_PARA_TO_APP);
    
    	public final static UUID UUID_HEALTH_SMOKE_PARA_FROM_APP =
            UUID.fromString(SampleGattAttributes.HEALTH_SMOKE_PARA_FROM_APP);

    	public final static UUID UUID_HEALTH_SMOKE_PICT_FROM_APP =
            UUID.fromString(SampleGattAttributes.HEALTH_SMOKE_PICT_FROM_APP);

		public final static UUID UUID_HEALTH_OAD_IDENTIFY =
            UUID.fromString(SampleGattAttributes.HEALTH_OAD_IDENTIFY);

    	public final static UUID UUID_HEALTH_OAD_BLOCK =
            UUID.fromString(SampleGattAttributes.HEALTH_OAD_BLOCK);

    	public final static UUID UUID_CONN_SET =
			UUID.fromString(SampleGattAttributes.HEALTH_CONN_SET);

		Handler handler = new Handler(); 
		Runnable runnable=new Runnable(){
			 @Override
			 public void run() {
				if (mSendCount == 0)
				{
					int i;
					boolean status;
					/**
					 * filelen为传递数据大小的字节
					 */
					byte[] filelen = new byte[4];
					byte[] dataByte = {(byte)0xAA,													// 起始位
										0x28, 0x00, 0x00, 0x00, 											// 数据长度
										(byte)0xEC, 																	// 数据校验位
										0x09, 																				// 文件名长度
										't', 'e', 's', 't', '5', '.', 'j', 'p', 'g',	// 文件名
										0x00, 0x00, 0x00, 0x00}; 	//如果文件名字不到20位后面直接补0就好。
					try {
						filelen[0] = (byte)streampic.available();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					try {
						filelen[0] = (byte)((streampic.available()) & 0x000000ff);
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					try {
						filelen[1] = (byte)((streampic.available() & 0x0000ff00) >> 8);
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					try {
						filelen[2] = (byte)((streampic.available() & 0x00ff0000) >> 16);
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					try {
						filelen[3] = (byte)((streampic.available() & 0xff000000) >> 24);
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					dataByte[1] = filelen[0];
					dataByte[2] = filelen[1];
					dataByte[3] = filelen[2];
					dataByte[4] = filelen[3];
					//设置要写的值
					characteristic_sendpic.setValue(dataByte);
					// status如果为true，表示写操作已经成功执行，BluetoothGattCallback抽象类的一个方法会被执行，
					// 如果刚好你又重写了这个方法，就可以打印一些消息了
					status=mBluetoothGatt.writeCharacteristic(characteristic_sendpic);
					System.out.println("Write Status: " + status);
					mSendCount++;
					displayWarnningInfo("开始传输test5.jpg文件");
					handler.postDelayed(runnable, 500);
				}
				else if (mretrytran == 1)
				{
					boolean status;
					mretrytran = 0;
					characteristic_sendpic.setValue(mdataByte);
					status = mBluetoothGatt.writeCharacteristic(characteristic_sendpic);
					if (status == false)
					{
						System.out.println("Write Status: false");
						mretrytran = 1;
					}
					handler.postDelayed(runnable, 10);
				}	
				else if (mSendCount <= mtotal)
				{
					boolean status;
					byte[] dataByte1 = new byte[20];
					dataByte1[0] = (byte)0x55;
					try {
						streampic.read(dataByte1, 1, TRANSMIT_LEN);
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					dataByte1[17] = 0x00;
					dataByte1[18] = 0x00;
					dataByte1[19] = 0x00;
					// 第一个是要复制的数组，第二个是从要复制的数组的第几个开始，第三个是复制到那，
					// 四个是复制到的数组第几个开始，最后一个是复制长度
					System.arraycopy(dataByte1, 0, mdataByte, 0, 20);
					characteristic_sendpic.setValue(dataByte1);
					status = mBluetoothGatt.writeCharacteristic(characteristic_sendpic);
					if (status == false)
					{
						System.out.println("Write Status: false");
						mretrytran = 1;
					}
					else						
						mSendCount++;
					String s = String.format("传输中......%d", mSendCount*100/mtotal);
					displayWarnningInfo(s);
					handler.postDelayed(runnable, 10);
				}
				else if (mSendCount > mtotal)
				{
					int len = 0;
					try {
						len = streampic.available()%TRANSMIT_LEN;
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					if (len > 0)
					{
						boolean status;
						byte[] dataByte1 = new byte[20];
						dataByte1[0] = (byte)0x55;
						try {
							streampic.read(dataByte1, 1, streampic.available()%TRANSMIT_LEN);
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						characteristic_sendpic.setValue(dataByte1);
						status=mBluetoothGatt.writeCharacteristic(characteristic_sendpic);
						if (status == false)
						System.out.println("Write Status: " + status);
					}
					mSendCount = 0;
					try {
						streampic.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
					displayWarnningInfo("传输完成!");
					handler.removeCallbacks(runnable);
				}
			 } 
		};

		public void getOADService() {
			Log.d(TAG, "construct");
			mDeviceActivity = DeviceControlActivity.getInstance();
		
			// Service information
			mOadService = mDeviceActivity.getOadService();
			mConnControlService = mDeviceActivity.getConnControlService();
		
			// Characteristics list
			mCharListOad = mOadService.getCharacteristics();
			if (mConnControlService != null)
  				mCharListCc = mConnControlService.getCharacteristics();
		
				mServiceOk = mCharListOad.size() == 2; //  && mCharListCc.size() >= 3;
			if (mServiceOk) {
				mCharIdentify = mCharListOad.get(0);
				mCharBlock = mCharListOad.get(1);
				if (mConnControlService != null)
  			mCharConnReq = mCharListCc.get(1);
			}
			
			getTargetImageInfo();
		}

    // Implements callback methods for GATT events that the app cares about.  For example,
	// 实施关贸总协定的事件，程序关注回调方法。例如,
    // connection change and services discovered.连接更改和服务发现。
	//连接后会回调BluetoothGattCallback接口，包括读取设备
	// ，往设备里写数据及设备发出通知等都会回调该接口。其中比较重要的是BluetoothGatt。
    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
		//当连接上设备或者失去连接时会回调该函数
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            String intentAction;
			/**
			 * 在这里发送广播，把链接的状态通过广播发送出去。
			 */
            if (newState == BluetoothProfile.STATE_CONNECTED) {//连接成功
                intentAction = ACTION_GATT_CONNECTED;
                mConnectionState = STATE_CONNECTED;
                broadcastUpdate(intentAction);
                Log.i(TAG, "Connected to GATT server.");
                // Attempts to discover services after successful connection.
                Log.i(TAG, "Attempting to start service discovery:" +
                        mBluetoothGatt.discoverServices());

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = ACTION_GATT_DISCONNECTED;
                mConnectionState = STATE_DISCONNECTED;
                Log.i(TAG, "Disconnected from GATT server.");
                broadcastUpdate(intentAction);
            }
        }
		// //当设备是否找到服务时，会回调该函数
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }
		//当读取设备时会回调该函数
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }
		//设备发出通知时会调用到该接口
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
        }
		//当向Characteristic写数据时会回调该函数
				@Override
        public void onCharacteristicWrite(BluetoothGatt gatt,
                                     	BluetoothGattCharacteristic characteristic, int status) {
//        	Log.w(TAG, "onCharacteristicWrite received: " + status);
			 	}
				
		@Override
		public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
			mBusy = false;
			Log.i(TAG, "onDescriptorRead");
		}
				
		@Override
		public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
			mBusy = false;
			Log.i(TAG, "onDescriptorWrite");
		}
    };
	/**
	 * 发送广播，一个参数方法。
	 */
    private void broadcastUpdate(final String action) {
        final Intent intent = new Intent(action);
        sendBroadcast(intent);
		mBusy = false;
    }
	/**
	 * 两个参数方法 。
	 */
    private void broadcastUpdate(final String action,
                                 final BluetoothGattCharacteristic characteristic) {
        final Intent intent = new Intent(action);

        // This is special handling for the Heart Rate Measurement profile.
		// 这是特殊处理的心脏心率测量剖面。
        // Data parsing is carried out as per profile specifications:
		// 按每个配置文件规范进行数据分析
				intent.putExtra(EXTRA_UUID, characteristic.getUuid().toString());
				intent.putExtra(EXTRA_DATA, characteristic.getValue());
        		sendBroadcast(intent);
				mBusy = false;
    }

    public class LocalBinder extends Binder {
        BluetoothLeService getService() {
            return BluetoothLeService.this;
        }
    }

	public String getInnerSDCardPath()
		{
			return Environment.getExternalStorageDirectory().getPath();
		}
		

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        // After using a given device, you should make sure that BluetoothGatt.close() is called
        // such that resources are cleaned up properly.  In this particular example, close() is
        // invoked when the UI is disconnected from the Service.
        close();
        return super.onUnbind(intent);
    }

    private final IBinder mBinder = new LocalBinder();

    /**
     * Initializes a reference to the local Bluetooth adapter.
     *
     * @return Return true if the initialization is successful.
     */
    public boolean initialize() {
        // For API level 18 and above, get a reference to BluetoothAdapter through
        // BluetoothManager.
        if (mBluetoothManager == null) {
            mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            if (mBluetoothManager == null) {
                Log.e(TAG, "Unable to initialize BluetoothManager.");
                return false;
            }
        }

        mBluetoothAdapter = mBluetoothManager.getAdapter();
        if (mBluetoothAdapter == null) {
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
            return false;
        }

        return true;
    }

    /**
     * Connects to the GATT server hosted on the Bluetooth LE device.
     * 连接到托管在蓝牙LE设备GATT服务器。
     * @param address The device address of the destination（目的地） device.
	 *
     * @return Return true if the connection is initiated successfully.
	 * 	如果连接成功启动，则返回真值
	 * The connection result is reported asynchronously through the
	 * 连接的结果是通过异步异步报告的
	 * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt,
	 * int, int)} callback.
     */
    public boolean connect(final String address) {
        if (mBluetoothAdapter == null || address == null) {
            Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
            return false ;
        }

        // Previously connected device.  Try to reconnect.
		// 以前连接的设备。尝试重新连接。
        if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
                && mBluetoothGatt != null) {
            Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
            if (mBluetoothGatt.connect()) {
                mConnectionState = STATE_CONNECTING;
                return true;
            } else {
                return false;
            }
        }

        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        if (device == null) {
			Log.w(TAG, "Device not found.  Unable to connect.");
			return false;
		}
        // We want to directly connect to the device, so we are setting the autoConnect
		//我们要直接连接到设备，所以我们设置自动连接
        // parameter to false.参数错误
        mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
        Log.d(TAG, "Trying to create a new connection.");
        mBluetoothDeviceAddress = address;
        mConnectionState = STATE_CONNECTING;
        return true;
    }

    /**
     * Disconnects an existing connection or cancel a pending connection. The disconnection result
     * is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public void disconnect() {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.disconnect();
    }

    /**
     * After using a given BLE device, the app must call this method to ensure resources are
     * released properly.
	 * 使用一个给定的BLE设备后，应用程序必须调用这个方法来确保资源是正确的。
     */
    public void close() {
        if (mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.close();
        mBluetoothGatt = null;
    }


    /**
     * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
	 * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
	 * callback.
     *
     * @param characteristic The characteristic to read from.
     */
    public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
		mBusy = true;
        mBluetoothGatt.readCharacteristic(characteristic);
    }

    /**
     * Enables or disables notification on a give characteristic.
     * 启用或禁用一个给定特性的通知。
     * @param characteristic Characteristic to act on.
     * @param enabled If true, enable notification.  False otherwise.
     */
    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
                                              boolean enabled) {
	    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
	        Log.w(TAG, "BluetoothAdapter not initialized");
	        return;
	    }
		
      if (UUID_HEALTH_SMOKE_CMD.equals(characteristic.getUuid())) {
				return;
      }
		
      if (UUID_HEALTH_SMOKE_PARA_FROM_APP.equals(characteristic.getUuid())) {
				return;
			}		

      if (UUID_HEALTH_SMOKE_PICT_FROM_APP.equals(characteristic.getUuid())) {
  			return;
  		}		

      if (UUID_HEALTH_OAD_BLOCK.equals(characteristic.getUuid())) {
  			return;
  		}		

      mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
			Log.e("BluetoothLeService", "setCharacteristicNotification: "+characteristic.getUuid());

			mBusy = true;
      
      	// This is specific to Health Smoke Info.
		// 这是特定的健康烟雾信息。
      if (UUID_HEALTH_SMOKE_INFO.equals(characteristic.getUuid())) {
          BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
                  UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
          descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
          mBluetoothGatt.writeDescriptor(descriptor);
      }
			
      // This is specific to Health Smoke commond.
      if (UUID_HEALTH_SMOKE_CMD.equals(characteristic.getUuid())) {
          BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
                  UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
          descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
          mBluetoothGatt.writeDescriptor(descriptor);
      }
			
      // This is specific to Health Smoke para to app.
      if (UUID_HEALTH_SMOKE_PARA_TO_APP.equals(characteristic.getUuid())) {
          BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
                  UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
          descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
          mBluetoothGatt.writeDescriptor(descriptor);
      }
			
      // This is specific to Health Smoke para from app.
      if (UUID_HEALTH_SMOKE_PARA_FROM_APP.equals(characteristic.getUuid())) {
          BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
                  UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
          descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
          mBluetoothGatt.writeDescriptor(descriptor);
      }

			// This is specific to Health Smoke pict from app.
			if (UUID_HEALTH_SMOKE_PICT_FROM_APP.equals(characteristic.getUuid())) {
				 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
								 UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
				 descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
				 mBluetoothGatt.writeDescriptor(descriptor);
			}

			// This is specific to Health oad identify.
			if (UUID_HEALTH_OAD_IDENTIFY.equals(characteristic.getUuid())) {
				 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
								 UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
				 descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
				 mBluetoothGatt.writeDescriptor(descriptor);
			}

			Log.e("BluetoothLeService", "writeDescriptor: "+characteristic.getUuid());
    }
	/**
	 * 时间和日期
	 */
    public void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
		
			Log.e("BluetoothLeService", "writeCharacteristic: "+characteristic.getUuid());
			
			mBusy = true;
	    if (UUID_HEALTH_SMOKE_CMD.equals(characteristic.getUuid())) {
	    	boolean status;
				Calendar date = Calendar.getInstance();
				StringBuilder data = new StringBuilder();
				String data_date_y = String.format("%2s", (date.get(Calendar.YEAR)-2000)).replace(' ', '0');
				data.append(data_date_y);
				byte datayear = (byte)(date.get(Calendar.YEAR)-2000);
				byte datamonth = (byte)(date.get(Calendar.MONTH));
				datamonth += 1;
				byte datadate = (byte)(date.get(Calendar.DATE));
				byte datahour = (byte)(date.get(Calendar.HOUR_OF_DAY));
				byte dataminute = (byte)(date.get(Calendar.MINUTE));
				byte datasecond = (byte)(date.get(Calendar.SECOND));
				byte datasum = datayear;
				datasum += datamonth;
				datasum += datadate;
				datasum += datahour;
				datasum += dataminute;
				datasum += datasecond;
				datasum += 0xAA;
				datasum += 0x05;
				datasum += 0x06;
				datasum += 0x00;
				byte[] dataByte1 = {(byte)0xAA, 0x05, 0x06, 0x00, datayear, datamonth, datadate, datahour, dataminute, datasecond, datasum};

				characteristic.setValue(dataByte1);
				status=mBluetoothGatt.writeCharacteristic(characteristic);
				System.out.println("Write Status: " + status);

	/*					byte[] dataByte2 = {(byte)0xAA, 0x00, 0x00, 0x00, (byte)0xAA};

				characteristic.setValue(dataByte2);
				status=mBluetoothGatt.writeCharacteristic(characteristic);
				System.out.println("Write Status: " + status);*/
	    }

			if (UUID_HEALTH_SMOKE_PARA_FROM_APP.equals(characteristic.getUuid())) {
				byte[] dataByte = {0x01, 0x00, 0x00, 0x00,
						0x01, 0x00,
						0x01, 0x00,
						0x03, 0x20,
						33,
						52,
						22,
						10,
						0x04,
						0x10,
						0x00, 0x00, 0x00, 0x00};
				
				characteristic.setValue(dataByte);
				boolean status=mBluetoothGatt.writeCharacteristic(characteristic);
				System.out.println("Write Status: " + status);
			}
			
			if (UUID_HEALTH_SMOKE_PICT_FROM_APP.equals(characteristic.getUuid())) {
				characteristic_sendpic = characteristic;
				mSendCount = 0;
				mretrytran = 0;			
				handler.removeCallbacks(runnable);
				mfile = new File("/sdcard/image", "IMG_0122.JPG");
				if(mfile.exists())
				{
					try
	        {
						streampic = new FileInputStream(mfile);
						mtotal = (streampic.available()/TRANSMIT_LEN);
						handler.postDelayed(runnable, 20); 
					}
					catch (IOException e) 
	        { 
	          e.printStackTrace(); 
	        } 
				}
			}

			if (UUID_CONN_SET.equals(characteristic.getUuid())) {
				// Make sure connection interval is long enough for OAD
				// 确保连接间隔足够长的道路
				byte[] value = { loUint16(OAD_CONN_MIN_INTERVAL), hiUint16(OAD_CONN_MIN_INTERVAL), loUint16(OAD_CONN_MAX_INTERVAL),
						hiUint16(OAD_CONN_MAX_INTERVAL), 0, 0, loUint16(OAD_SUPERVISION_TIMEOUT), hiUint16(OAD_SUPERVISION_TIMEOUT) };
				
				mCharConnReq.setValue(value);
				mBluetoothGatt.writeCharacteristic(mCharConnReq);
			}
			
			if (UUID_HEALTH_OAD_IDENTIFY.equals(characteristic.getUuid())) {
//				mCharIdentify = characteristic;
				mImageVer = false;
				// Read target image info
				mUpdateFirmwareOn = true;
				getTargetImageInfo();
			}

			if (UUID_HEALTH_OAD_BLOCK.equals(characteristic.getUuid())) {
//				mCharBlock = characteristic;
				mImageVer = true;
				// Read target image info
				mUpdateFirmwareOn = true;
				getTargetImageInfo();
			}
    }

		public void updateBTFirmware()
		{
			if (mUpdateFirmwareOn == false)
				return;
			
			mUpdateFirmwareOn = false;
			if (mImageVer == false)
			{
				if (loadFile("/sdcard/image/SimpleBLEPeripheralA.bin", false) == true)
					startProgramming();
			}
			else
			{
				if (loadFile("/sdcard/image/SimpleBLEPeripheralB.bin", false) == true)
					startProgramming();
			}
		}
		
    /**
     * Retrieves a list of supported GATT services on the connected device. This should be
     * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
     *
     * @return A {@code List} of supported services.
     */
    public List<BluetoothGattService> getSupportedGattServices() {
        if (mBluetoothGatt == null) return null;

        return mBluetoothGatt.getServices();
    }

	  private boolean loadFile(String filepath, boolean isAsset) {
	    boolean fSuccess = false;

	    // Load binary file
	    try {
	      // Read the file raw into a buffer
	      InputStream stream;
	      if (isAsset) {
	        stream = getAssets().open(filepath);
	      } else {
	        File f = new File(filepath);
	        stream = new FileInputStream(f);
	      }
	      stream.read(mFileBuffer, 0, mFileBuffer.length);
	      stream.close();
	    } catch (IOException e) {
	      // Handle exceptions here
				System.out.println("File open failed:" + filepath);
				
				displayWarnningInfo("File open failed:" + filepath);
	      return false;
	    }

	    // Show image info
	    mFileImgHdr.ver = buildUint16(mFileBuffer[5], mFileBuffer[4]);
	    mFileImgHdr.len = buildUint16(mFileBuffer[7], mFileBuffer[6]);
	    mFileImgHdr.imgType = ((mFileImgHdr.ver & 1) == 1) ? 'B' : 'A';
	    System.arraycopy(mFileBuffer, 8, mFileImgHdr.uid, 0, 4);
      displayImageInfo(mFileImgHdr);

      // Verify image types
      boolean ready = mFileImgHdr.imgType != mTargImgHdr.imgType;
//      int resid = ready ? R.style.dataStyle1 : R.style.dataStyle2;
//	    mFileImage.setTextAppearance(this, resid);

	    // Expected duration
	    mEstDuration = ((PKT_INTERVAL * mFileImgHdr.len * 4) / OAD_BLOCK_SIZE) / 1000;
	    displayStats();

	    // Log
			System.out.println("Image " + mFileImgHdr.imgType + " selected.\n");
			System.out.println(ready ? "Ready to program device!\n" : "Incompatible image, select alternative!\n");

			displayWarnningInfo(ready ? "蓝牙固件更新......\n" : "固件版本错误，请重试!\n");

			if (ready == true)
				fSuccess = true;
			
	    return fSuccess;
	  }

			
		private void displayStats() {
			String txt;
			int byteRate;
			int sec = mProgInfo.iTimeElapsed / 1000;
			if (sec > 0) {
				byteRate = mProgInfo.iBytes / sec;
			} else {
				byteRate = 0;
			}
		
			txt = String.format("Time: %d / %d sec", sec, mEstDuration);
			txt += String.format("		Bytes: %d (%d/sec)", mProgInfo.iBytes, byteRate);
			System.out.println(txt);
		}

		public void displayImageInfo(ImgHdr h) {
			int imgVer = (h.ver) >> 1;
			int imgSize = h.len * 4;
			String s = String.format("Type: %c Ver.: %d Size: %d", h.imgType, imgVer, imgSize);
			
			System.out.println(s);
		}

		private void startProgramming() {
			System.out.println("Programming started\n");
			 mProgramming = true;
		
			 // Prepare image notification
			 byte[] buf = new byte[OAD_IMG_HDR_SIZE + 2 + 2];
			 buf[0] = loUint16(mFileImgHdr.ver);
			 buf[1] = hiUint16(mFileImgHdr.ver);
			 buf[2] = loUint16(mFileImgHdr.len);
			 buf[3] = hiUint16(mFileImgHdr.len);
			 System.arraycopy(mFileImgHdr.uid, 0, buf, 4, 4);
		
			 // Send image notification
			 mCharIdentify.setValue(buf);
			 mBluetoothGatt.writeCharacteristic(mCharIdentify);
		
			 // Initialize stats
			 mProgInfo.reset();
		
			 // Start the packet timer
			 mTimer = null;
			 mTimer = new Timer();
			 mTimerTask = new ProgTimerTask();
			 mTimer.scheduleAtFixedRate(mTimerTask, 0, PKT_INTERVAL);
		}
		
		private void stopProgramming() {
			 mTimer.cancel();
			 mTimer.purge();
			 mTimerTask.cancel();
			 mTimerTask = null;
			 mProgInfo.nRetryNum = 0;

			 mProgramming = false;

			 if (mProgInfo.iBlocks == mProgInfo.nBlocks) {
				 System.out.println("Programming complete!\n");
			 } else {
				 System.out.println("Programming cancelled!\n");
			 }
		}

			private void onBlockTimer() {
			
				if (mProgInfo.iBlocks < mProgInfo.nBlocks) {
					mProgramming = true;
			
					// Prepare block
					mOadBuffer[0] = loUint16(mProgInfo.iBlocks);
					mOadBuffer[1] = hiUint16(mProgInfo.iBlocks);
					System.arraycopy(mFileBuffer, mProgInfo.iBytes, mOadBuffer, 2, OAD_BLOCK_SIZE);
			
					// Send block
					mCharBlock.setValue(mOadBuffer);
					boolean success = mBluetoothGatt.writeCharacteristic(mCharBlock);
			
					if (success) {
						// Update stats
						mProgInfo.iBlocks++;
						mProgInfo.iBytes += OAD_BLOCK_SIZE;
						
						String s = String.format("蓝牙固件更新...... %d", ((mProgInfo.iBlocks * 100) / mProgInfo.nBlocks));
						displayWarnningInfo(s);
					} else {
						// Check if the device has been prematurely disconnected
						if ((mBluetoothGatt == null) || (mProgInfo.nRetryNum > 1000)) {
							mProgramming = false;
							displayWarnningInfo("蓝牙固件更新失败!");
						}
						else
							mProgInfo.nRetryNum++;
					}
				} else {
					mProgramming = false;					
					
					displayWarnningInfo("蓝牙固件更新完成!等待蓝牙自动断开连接，不能自动断开连接表示升级成败!");
				}
				mProgInfo.iTimeElapsed += PKT_INTERVAL;
			
  			if (!mProgramming) {
//  				runOnUiThread(new Runnable() {
//  					public void run() {
  						displayStats();
  						stopProgramming();
//  					}
//  				});
  			}
			}

			private class ProgTimerTask extends TimerTask {
				@Override
				public void run() {
					mProgInfo.mTick++;
					if (mProgramming) {
						onBlockTimer();
						if ((mProgInfo.mTick % PKT_INTERVAL) == 0) {
//							runOnUiThread(new Runnable() {
//								public void run() {
//									displayStats();
//								}
//							});
						}
					}
				}
			}

	public class ImgHdr {
    short ver;
    short len;
    Character imgType;
    byte[] uid = new byte[4];
  }

  private class ProgInfo {
    	int iBytes = 0; // Number of bytes programmed       	编程字节数
    	short iBlocks = 0; // Number of blocks programmed   	程序块数
    	short nBlocks = 0; // Total number of blocks     		块的总数
    	int iTimeElapsed = 0; // Time elapsed in milliseconds   以毫秒为单位的时间
    	int mTick = 0;
		short nRetryNum = 0;

	  void reset() {
      	iBytes = 0;
      	iBlocks = 0;
      	iTimeElapsed = 0;
      	mTick = 0;
		nRetryNum = 0;
      	nBlocks = (short) (mFileImgHdr.len / (OAD_BLOCK_SIZE / HAL_FLASH_WORD_SIZE));
    }
  }

  private static byte loUint16(short v) {
    return (byte) (v & 0xFF);
  }

  private static byte hiUint16(short v) {
    return (byte) (v >> 8);
  }

  private static short buildUint16(byte hi, byte lo) {
    return (short) ((hi << 8) + (lo & 0xff));
  }

  public static String BytetohexString(byte[] b, int len) {
    StringBuilder sb = new StringBuilder(b.length * (2 + 1));
    Formatter formatter = new Formatter(sb);

    for (int i = 0; i < len; i++) {
      if (i < len - 1)
        formatter.format("%02X:", b[i]);
      else
        formatter.format("%02X", b[i]);

    }
    formatter.close();

    return sb.toString();
  }

  static String BytetohexString(byte[] b, boolean reverse) {
    StringBuilder sb = new StringBuilder(b.length * (2 + 1));
    Formatter formatter = new Formatter(sb);

    if (!reverse) {
      for (int i = 0; i < b.length; i++) {
        if (i < b.length - 1)
          formatter.format("%02X:", b[i]);
        else
          formatter.format("%02X", b[i]);

      }
    } else {
      for (int i = (b.length - 1); i >= 0; i--) {
        if (i > 0)
          formatter.format("%02X:", b[i]);
        else
          formatter.format("%02X", b[i]);

      }
    }
    formatter.close();

    return sb.toString();
  }

  // Convert hex String to Byte
  public static int hexStringtoByte(String sb, byte[] results) {

    int i = 0;
    boolean j = false;

    if (sb != null) {
      for (int k = 0; k < sb.length(); k++) {
        if (((sb.charAt(k)) >= '0' && (sb.charAt(k) <= '9')) || ((sb.charAt(k)) >= 'a' && (sb.charAt(k) <= 'f'))
            || ((sb.charAt(k)) >= 'A' && (sb.charAt(k) <= 'F'))) {
          if (j) {
            results[i] += (byte) (Character.digit(sb.charAt(k), 16));
            i++;
          } else {
            results[i] = (byte) (Character.digit(sb.charAt(k), 16) << 4);
          }
          j = !j;
        }
      }
    }
    return i;
  }

  public static boolean isAsciiPrintable(String str) {
    if (str == null) {
      return false;
    }
    int sz = str.length();
    for (int i = 0; i < sz; i++) {
      if (isAsciiPrintable(str.charAt(i)) == false) {
        return false;
      }
    }
    return true;
  }

  private static boolean isAsciiPrintable(char ch) {
    return ch >= 32 && ch < 127;
  }

  private boolean getTargetImageInfo() {
    // Enable notification
    boolean ok = enableNotification(mCharIdentify, true);
    // Prepare data for request (try image A and B respectively, only one of
    // them will give a notification with the image info)
		delayMS(100);
    if (ok)
      ok = writeCharacteristic(mCharIdentify, (byte) 0);
		delayMS(100);
    if (ok)
      ok = writeCharacteristic(mCharIdentify, (byte) 1);
		delayMS(100);
    if (!ok) {
			displayWarnningInfo("Failed to get target info");
			System.out.println("Failed to get target info");
    }

		return ok;
  }

  private void setConnectionParameters() {
		if (mConnControlService == null)
			return;
		
    // Make sure connection interval is long enough for OAD
    byte[] value = { loUint16(OAD_CONN_MIN_INTERVAL), hiUint16(OAD_CONN_MIN_INTERVAL), loUint16(OAD_CONN_MAX_INTERVAL),
        hiUint16(OAD_CONN_MAX_INTERVAL), 0, 0, loUint16(OAD_SUPERVISION_TIMEOUT), hiUint16(OAD_SUPERVISION_TIMEOUT) };

		mCharConnReq.setValue(value);
    mBluetoothGatt.writeCharacteristic(mCharConnReq);
    waitIdle(GATT_WRITE_TIMEOUT);
  }

  private boolean enableNotification(BluetoothGattCharacteristic c, boolean enable) {
    boolean ok = true;
		setCharacteristicNotification(c, enable);
    if (ok)
      ok = waitIdle(GATT_WRITE_TIMEOUT);
    return true;
  }
	
  public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic, byte b) {
    byte[] val = new byte[1];
    val[0] = b;
    characteristic.setValue(val);

    mBusy = true;
    return mBluetoothGatt.writeCharacteristic(characteristic);
  }

  public boolean waitIdle(int i) {
    i /= 10;
    while (--i > 0) {
      if (mBusy)
        try {
          Thread.sleep(10);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      else
        break;
    }

    return i > 0;
  }

  public void delayMS(int i) {
    while (--i > 0) {
      try {
        Thread.sleep(1);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

	private void displayWarnningInfo(String str) {
		final Intent intent = new Intent(ACTION_WARNNING);
		intent.putExtra(EXTRA_DATA, str);
		sendBroadcast(intent);
	}
}
